﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Rappelz.GameServer
{
    public class ArRegionContainer
    {
        private const int REGION_BLOCK_COUNT = 100;

        public static uint[] s_Matrix = {0,0,0,0,0,0,0,
                                         0,0,1,1,1,0,0,
                                         0,1,1,1,1,1,0,
                                         0,1,1,1,1,1,0,
                                         0,1,1,1,1,1,0,
                                         0,0,1,1,1,0,0,
                                         0,0,0,0,0,0,0};

/*
        public static uint[] s_Matrix = {0,0,1,1,1,0,0,
                                         0,1,1,1,1,1,0,
                                         1,1,1,1,1,1,1,
                                         1,1,1,1,1,1,1,
                                         1,1,1,1,1,1,1,
                                         0,1,1,1,1,1,0,
                                         0,0,1,1,1,0,0};
*/


        public class ArRegionBlock
        {
            public class ArRegionBase
            {
                public ArRegion[] m_Regions = new ArRegion[REGION_BLOCK_COUNT * REGION_BLOCK_COUNT];
            }
            // UserDefinedType:   ArRegionBlock
            // Function       :     public void ArRegionBlock(const struct ArRegionContainer::ArRegionBlock &)
            // Function       :     public void ArRegionContainer::ArRegionBlock::ArRegionBlock()
            // Function       :     public void ArRegionContainer::ArRegionBlock::~ArRegionBlock()

            public ArRegion getRegionPtr(uint rx, uint ry, byte layer)
            {
                ArRegion res = null;

                lock(this)
                {
                    ArRegionBase rb = this.m_RegionBases[layer];
                    if (rb != null)
                        res = rb.m_Regions[ry + (REGION_BLOCK_COUNT * rx)];
                }
                return res;
            }

            public ArRegion getRegion(uint rx, uint ry, byte layer)
            {
                ArRegion res = null;
                lock(this)
                {
                    ArRegionBase rb = this.m_RegionBases[layer];
                    if (rb == null)
                    {
                        rb = new ArRegionBase();
                        this.m_RegionBases[layer] = rb;
                    }
                    res = rb.m_Regions[rx + (REGION_BLOCK_COUNT * ry)];
                    if (res == null)
                    {
                        res = new ArRegion();
                        res.x = rx;
                        res.y = ry;
                        res.layer = layer;
                        rb.m_Regions[rx + (REGION_BLOCK_COUNT * ry)] = res;
                    }
                }
                return res;
            }


            public ArRegionBase[] m_RegionBases = new ArRegionBase[256];
        }

// UserDefinedType: ArRegionContainer
// Enum           :   <unnamed-tag>, Type: int
// Data           :     constant 0x64, Constant, Type: int, 
// Function       :   public void ArRegionContainer(const struct ArRegionContainer &)
// Function       :   public void ArRegionContainer::ArRegionContainer(float, float)
        public ArRegionContainer(float map_width, float map_height)
        {
//            XSpinLock::XSpinLock(&this->m_Lock, &unk_5F42BB);
            this.m_MapWidth = map_width;
            this.m_MapHeight = map_height;
            this.m_nRegionWidth = (uint)((map_width / GameContent.g_nRegionSize) + 1.0f);
            this.m_nRegionHeight = (uint)((map_height / GameContent.g_nRegionSize) + 1.0f);
            this.m_nRegionBlockWidth = (this.m_nRegionWidth / REGION_BLOCK_COUNT) + 1;
            this.m_nRegionBlockHeight = (this.m_nRegionHeight / REGION_BLOCK_COUNT) + 1;
            this.initRegion();
        }
// Function       :   public void ArRegionContainer::~ArRegionContainer()
// Function       :   public bool IsValidPosition(const struct ArPosition &)
// Function       :   public bool IsValidPosition(float, float)

        public bool IsValidRegion(uint rx, uint ry, byte layer)
        {
            return rx < this.m_nRegionWidth && ry < this.m_nRegionHeight;
        }
// Function       :   public struct ArRegion * IfClientExistRegion(uint rx, uint ry, byte layer)
// Function       :   public struct ArRegion * IfExistRegion(uint rx, uint ry, byte layer)

        public ArRegion GetRegion(BaseObject pObject)
        {
            float rs = (float) GameContent.g_nRegionSize;
            return this.GetRegion((uint)(pObject.mv.x / rs), (uint)(pObject.mv.y / rs), pObject.m_layer);
        }

// Function       :   public struct ArRegion * GetRegion(const struct ArPosition &, byte layer)
        public ArRegion GetRegion(uint rx, uint ry, byte layer)
        {
            ArRegion result = null;

            if (this.IsValidRegion(rx, ry, layer))
                result = this.getRegion(rx, ry, layer);
            return result;
        }

// Function       :   public struct ArRegion * ArRegionContainer::GetRegion(uint rx, uint ry, byte layer)
// Function       :   public void ArRegionContainer::DoEachSpecificRegion(unsigned int, unsigned int, unsigned int, unsigned char, struct ArRegionFunctor &)

        public void DoEachVisibleRegion(uint rx1, uint ry1, uint rx2, uint ry2, byte layer, ArRegionFunctor fn)
        {
            uint right;
            uint top;
            uint bottom;
            uint left;

//             left = rx1;
//             if(rx2 < rx1)
//                 left = rx2;
// 
//             top = ry1;
//             if(ry2 < ry1)
//                 top = ry2;
// 
//             right = rx1;
//             if(rx2 > rx1)
//                 right = rx2;
//             if(right >= this.m_nRegionWidth)
//                 right = this.m_nRegionWidth-1;
// 
//             bottom = ry1;
//             if(ry2 > ry1)
//                 bottom = ry2;
//             if(bottom >= this.m_nRegionHeight)
//                 bottom = this.m_nRegionHeight-1;
// 
// 
//             right = rx2 - 3;
//             left = rx1 - 3;

            left = rx2 - 3;
            if ((rx2 - 3) >= (rx1 - 3))
                left = rx1 - 3;
            if (left <= 0)
                left = 0;
            
            top = ry2 - 3;
            if ((ry2 - 3) >= (ry1 - 3))
                top = ry1 - 3;
            if (top <= 0)
                top = 0;

            right = rx2 + 3;
            if ((rx1 + 3) >= (rx2 + 3))
                right = rx1 + 3;
            if (right >= this.m_nRegionWidth)
                right = this.m_nRegionWidth - 1;

            bottom = ry2 + 3;
            if ((ry1 + 3) >= (ry2 + 3))
                bottom = ry1 + 3;
            if (bottom >= this.m_nRegionHeight)
                bottom = this.m_nRegionHeight - 1;

            try
            {
                for(uint x = left; x <= right; ++x)
                {
                    for(uint y = top; y < bottom; ++y)
                    {
                        if (IsVisibleRegion(rx1, ry1, x, y) != 0 || IsVisibleRegion(rx2, ry2, x, y) != 0)
                        {
                            ArRegion region = this.getRegionPtr(x, y, layer);
                            if (region != null )
                                fn.run(region);
                        }
                    }
                }
            }
            catch (System.Exception ex)
            {
                Globals.Log.Error("Exception: {0}", ex);
            }
        }

        public void DoEachVisibleRegion(uint rx, uint ry, byte layer, ArRegionFunctor fn)
        {
            uint right;
            uint top;
            uint bottom;
            uint left;

            left = rx - 3;
            if(rx < 3)
                left = 0;

            top = ry - 3;
            if(ry < 3)
                top = 0;

            right = rx + 3;
            if(right >= this.m_nRegionWidth)
                right = this.m_nRegionWidth-1;

            bottom = ry + 3;
            if(bottom >= this.m_nRegionHeight)
                bottom = this.m_nRegionHeight-1;
            try
            {
                for(uint x = left; x <= right; ++x)
                {
                    for(uint y = top; y < bottom; ++y)
                    {
                        if (IsVisibleRegion(rx, ry, x, y) != 0)
                        {
                            ArRegion region = this.getRegionPtr(x, y, layer);
                            if (region != null )
                                fn.run(region);
                        }
                    }
                }
            }
            catch (System.Exception ex)
            {
                Globals.Log.Error("Exception: {0}", ex);
            }

        }

        public void DoEachNewRegion(uint rx, uint ry, uint prx, uint pry, byte layer, ArRegionFunctor fn)
        {
            uint right;
            uint top;
            uint bottom;
            uint left;

            left = rx - 3;
            if(rx < 3)
                left = 0;

            top = ry - 3;
            if(ry < 3)
                top = 0;

            right = rx + 3;
            if(right >= this.m_nRegionWidth)
                right = this.m_nRegionWidth-1;

            bottom = ry + 3;
            if(bottom >= this.m_nRegionHeight)
                bottom = this.m_nRegionHeight-1;

            try
            {
                for (uint x = left; x <= right; ++x)
                {
                    for (uint y = top; y < bottom; ++y)
                    {
                        if (IsVisibleRegion(rx, ry, x, y) != 0)
                        {
                            if (IsVisibleRegion(prx, pry, x, y) == 0)
                            {
                                ArRegion region = this.getRegionPtr(x, y, layer);
                                if (region != null)
                                    fn.run(region);
                            }
                        }
                    }
                }

            }
            catch (System.Exception ex)
            {
                Globals.Log.Error("Exception: {0}", ex);
            }

        }
// Function       :   public void ArRegionContainer::DoEachRegion(unsigned int, unsigned int, unsigned int, unsigned int, unsigned char, struct ArRegionFunctor &)

        public uint IsVisibleRegion(uint rx, uint ry, uint _rx, uint _ry)
        {
            uint result = 0;

            uint cx = _rx - rx + 3;
            uint cy = _ry - ry + 3;
            if (cx <= 6 && cy <= 6)
                result = s_Matrix[cx + (7 * cy)];
            return result;
        }

        private void initRegion()
        {
            lock(this)
            {
                uint count = this.m_nRegionBlockHeight * this.m_nRegionBlockWidth;
                this.m_RegionBlock = new ArRegionBlock[count];
            }
        }


        private void deinitRegion()
        {
            lock(this)
            {
                uint count = this.m_nRegionBlockHeight * this.m_nRegionBlockWidth;
                for(uint i = 0; i < count; ++i)
                {
                    ArRegionBlock b = this.m_RegionBlock[i];
                    if(b!= null)
                        b.m_RegionBases = null;
                }
                this.m_RegionBlock = null;
            }
        }

        private ArRegionBlock getRegionBlockPtr(uint rcx, uint rcy)
        {
            ArRegionBlock res = null;

            lock(this)
            {
                res = this.m_RegionBlock[rcx + rcy * this.m_nRegionBlockWidth];
            }
            return res;
        }

        private ArRegionBlock getRegionBlock(uint rcx, uint rcy)
        {
            ArRegionBlock res = null;

            lock(this)
            {
                res = this.m_RegionBlock[rcx + rcy * this.m_nRegionBlockWidth];

                if (res == null )
                {
                    res = new ArRegionBlock();
                    this.m_RegionBlock[rcx + rcy * this.m_nRegionBlockWidth] = res;
                }
            }
            return res;
        }
        // Function       :   private bool isExistRegion(uint rx, uint ry, byte layer)
 
        private ArRegion getRegionPtr(uint rx, uint ry, byte layer)
        {
            ArRegionBlock b = getRegionBlockPtr(rx / REGION_BLOCK_COUNT, ry / REGION_BLOCK_COUNT);
            if(b != null)
                return b.getRegion(rx % REGION_BLOCK_COUNT, ry % REGION_BLOCK_COUNT, layer);
            return null;
        }

        private ArRegion getRegion(uint rx, uint ry, byte layer)
        {
            ArRegionBlock b = getRegionBlock(rx / REGION_BLOCK_COUNT, ry / REGION_BLOCK_COUNT);
            if (b != null)
                return b.getRegion(rx % REGION_BLOCK_COUNT, ry % REGION_BLOCK_COUNT, layer);
            return null;
        }


        public float m_MapWidth;                        // 0x0
        public float m_MapHeight;                       // 0x4
        public uint m_nRegionWidth;                      // 0x8
        public uint m_nRegionHeight;                     // 0xC
        public uint m_nRegionBlockWidth;                // 0x10
        public uint m_nRegionBlockHeight;               // 0x14
        public ArRegionContainer.ArRegionBlock[] m_RegionBlock;        // 0x18, Member, Type: struct ArRegionContainer::ArRegionBlock * *, 
// 
// Function       :   public struct ArRegionContainer & operator=(const struct ArRegionContainer &)

    }
}
